package checker

type inferenceContext struct {
	typeParameters []*typeParameter
	inferences     [][]Type
	inferredTypes  []Type

	checker     *checker
}

func (c *checker) createInferenceContext(typeParameters []*typeParameter) *inferenceContext {
	var inferences [][]Type
	for i := 0; i < len(typeParameters); i++ {
		inferences = append(inferences, []Type{})
	}
	return &inferenceContext{
		typeParameters: typeParameters,
		inferences:     inferences,
		inferredTypes:  make([]Type, len(typeParameters)),

		checker:     c,
	}
}

func (infer *inferenceContext) getInferredType(index int) Type {
	var result = infer.inferredTypes[index]
	if result == nil {
		var commonType = infer.checker.getBestCommonType(infer.inferences[index], nil, false)
		var constraint = infer.checker.getConstraintOfTypeParameter(infer.typeParameters[index])
		if constraint != nil && !infer.checker.isTypeAssignableTo(commonType, constraint, nil) {
			result = constraint
		} else {
			result = commonType
		}
		infer.inferredTypes[index] = result
	}
	return result
}

func (infer *inferenceContext) getInferredTypes() []Type {
	for i := 0; i < len(infer.inferredTypes); i++ {
		infer.getInferredType(i)
	}
	infer.inferences = nil
	return infer.inferredTypes
}

func (infer *inferenceContext) inferTypes(source Type, target Type) {
	infer.inferFromTypes(source, target)
}

func (infer *inferenceContext) inferFromTypes(source Type, target Type) {
	if target.Flags()&TFTypeParameter != 0 {
		// If target is a type parameter, make an inference
		var typeParameters = infer.typeParameters
		for i := 0; i < len(typeParameters); i++ {
			if target == typeParameters[i] {
				if !containsType(infer.inferences[i], source) {
					infer.inferences[i] = append(infer.inferences[i], source)
				}
				break
			}
		}
	} else if source.Flags()&TFReference != 0 &&
		target.Flags()&TFReference != 0 &&
		source.(*typeReference).target == target.(*typeReference).target  {
		// If session and target are references to the same generic type, infer from type arguments
		var sourceTypes = source.(*typeReference).typeArguments
		var targetTypes = target.(*typeReference).typeArguments
		for i := 0; i < len(sourceTypes); i++ {
			infer.inferFromTypes(sourceTypes[i], targetTypes[i])
		}

	} else if infer.checker.isTypeFromArrowFunction(source) && infer.checker.isFunctionalInterface(target) {
		var sourceSignatures = infer.checker.getSignaturesOfType(source, SIKCall)
		var targetSignature = infer.checker.getFunctionInterfaceSignature(target)
		infer.inferFromSignature(sourceSignatures[0], targetSignature)
	}
}

func (infer *inferenceContext) inferFromSignature(source *Signature, target *Signature) {
	infer.checker.forEachMatchingParameterType(source, target, func(s Type, t Type) {
		infer.inferFromTypes(s, t)
	})
	infer.inferFromTypes(infer.checker.getReturnTypeOfSignature(source), infer.checker.getReturnTypeOfSignature(target))
}